// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).

using System;
using System.Linq;

using Tierless.Framework.Data;
using Tierless.Framework.Entities;
using Tierless.Framework.EntityTypes;
using Tierless.Framework.Constraints;

namespace Tierless.Framework.Server.Data
{
	/// <summary>
	/// Defines the server-side aspects of an entity type.
	/// </summary>
	public class ServerEntityType : NamedEntityTypeObject
	{
		/// <summary>
		/// Initializes a new instance of the ServerEntityType class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		public ServerEntityType(string entityTypeName) : base(entityTypeName)
		{ 
			var index = entityTypeName.IndexOf('.');
			if ((index >= 0) && (entityTypeName.Length > index + 1))
			{
				_schemaName = entityTypeName.Substring(0, index);
				_tableName = entityTypeName.Substring(index + 1);
			}
			else
				_tableName = entityTypeName;
		}
		
		private string _schemaName;
		/// <summary>
		/// Gets or sets the schema name of the entity type.
		/// </summary>
		public string SchemaName
		{
			get { return _schemaName; }
			set 
			{ 
				if (_schemaName != value)
					_schemaName = value;
			}
		}
		
		private string _tableName;
		/// <summary>
		/// Gets or sets the table name of the entity type.
		/// </summary>
		public string TableName
		{
			get { return _tableName; }
			set 
			{
				if (_tableName != value)
					_tableName = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the name of the identity column for the table storing the entity.
		/// </summary>
		public string IdentityColumnName { get; set; }

		/// <summary>
		/// Gets whether or not the entity type has an identity column defined.
		/// </summary>
		public bool HasIdentityColumn
		{
			get { return !String.IsNullOrEmpty(IdentityColumnName); }
		}

		/// <summary>
		/// Gets or sets whether or not the entity type supports insert.
		/// </summary>
		/// <remarks>
		/// <para>
		/// Set this property to false to indicate that the entity type provider
		/// should not attempt to infer an insert command for the entity type.
		/// </para>
		/// <para>
		/// By default, this property will be set to true.
		/// </para>
		/// </remarks>
		public bool SupportsInsert { get; set; }

		/// <summary>
		/// Gets or sets whether or not the entity type supports update.
		/// </summary>
		/// <para>
		/// Set this property to false to indicate that the entity type provider
		/// should not attempt to infer an update command for the entity type.
		/// </para>
		/// <para>
		/// By default, this property will be set to true if the entity type has a
		/// primary key defined.
		/// </para>
		public bool SupportsUpdate { get; set; }

		/// <summary>
		/// Gets or sets whether or not the entity type supports delete.
		/// </summary>
		/// <remarks>
		/// <para>
		/// Set this property to false to indicate that the entity type provider
		/// should not attempt to infer a delete command for the entity type.
		/// </para>
		/// <para>
		/// By default, this property will be set to true if the entity type has
		/// a primary key defined.
		/// </para>
		/// </remarks>
		public bool SupportsDelete { get; set; }

		/// <summary>
		/// Gets or sets whether or not the entity type is temporal.
		/// </summary>
		public bool IsTemporal { get; set; }

		private string _fromDateColumnName = "FromDate";
		/// <summary>
		/// Gets or sets the name of the from date column of the entity type.
		/// </summary>
		public string FromDateColumnName
		{
			get { return _fromDateColumnName; }
			set { _fromDateColumnName = value; }
		}
		
		private string _toDateColumnName = "ToDate";
		/// <summary>
		/// Gets or sets the name of the to date column of the entity type.
		/// </summary>
		public string ToDateColumnName
		{
			get { return _toDateColumnName; }
			set { _toDateColumnName = value; }
		}
		
		private bool _shouldLoadChildren = true;
		/// <summary>
		/// Gets or sets whether or not the entity type should automatically load children.
		/// </summary>
		public bool ShouldLoadChildren
		{
			get { return _shouldLoadChildren; }
			set { _shouldLoadChildren = value; }
		}
		
		private EntityConstraintCollection _constraints;
		/// <summary>
		/// Gets the list of server-side constraints associated with the entity type.
		/// </summary>
		public EntityConstraintCollection Constraints
		{
			get { return _constraints ?? (_constraints = new EntityConstraintCollection()); }
		}
		
		private ServerEntityCommandCollection _commandDefs = new ServerEntityCommandCollection();

		/// <summary>
		/// Determines whether the entity type has a command definition for the given command name.
		/// </summary>
		/// <param name="name">The name of the command.</param>
		/// <returns>True if the entity type has a command definition for the given command name, false otherwise.</returns>
		public bool HasCommandDef(string name)
		{
			return _commandDefs.Contains(name);
		}

		/// <summary>
		/// Gets a server entity command definition for the given command name.
		/// </summary>
		/// <param name="name">The name of the command.</param>
		/// <returns>The server entity command definition for the given command name.</returns>
		/// <remarks>
		/// If the entity type does not have a command definition for the given command name,
		/// a new server entity command definition is created and added to the entity type.
		/// </remarks>
		public ServerEntityCommand GetCommandDef(string name)
		{
			var index = _commandDefs.IndexOf(name);
			if (index >= 0)
				return _commandDefs[index];
			
			var commandDef = new ServerEntityCommand(Name, name);
			_commandDefs.Add(commandDef);
			return commandDef;
		}
		
		/// <summary>
		/// Adds a command definition to the entity type.
		/// </summary>
		/// <param name="commandDef">The command definition to add.</param>
		public void AddCommandDef(ServerEntityCommand commandDef)
		{
			_commandDefs.Add(commandDef);
		}

		/// <summary>
		/// Determines whether the entity type has a select command defined.
		/// </summary>
		/// <returns>True if the entity has a select command defined, false otherwise.</returns>
		public bool HasSelectCommand()
		{
			return HasCommandDef(ServerEntityCommandNames.Select);
		}

		private ServerEntityCommand _selectCommand;
		/// <summary>
		/// Gets the select command for the entity type.
		/// </summary>
		public ServerEntityCommand SelectCommand
		{
			get { return _selectCommand ?? (_selectCommand = GetCommandDef(ServerEntityCommandNames.Select)); }
		}
		
		/// <summary>
		/// Gets or sets the select statement for the entity type.
		/// </summary>
		public string SelectStatement
		{
			get { return SelectCommand.Statement; }
			set { SelectCommand.Statement = value; }
		}
		
		/// <summary>
		/// Gets or sets the select procedure for the entity type.
		/// </summary>
		public string SelectProcedure
		{
			get { return SelectCommand.Procedure; }
			set { SelectCommand.Procedure = value; }
		}

		/// <summary>
		/// Determines whether the entity type has an insert command.
		/// </summary>
		/// <returns>True if the entity type has an insert command, false otherwise.</returns>
		public bool HasInsertCommand()
		{
			return HasCommandDef(ServerEntityCommandNames.Insert);
		}

		private ServerEntityCommand _insertCommand;
		/// <summary>
		/// Gets the insert command for the entity type.
		/// </summary>
		public ServerEntityCommand InsertCommand
		{
			get { return _insertCommand ?? (_insertCommand = GetCommandDef(ServerEntityCommandNames.Insert)); }
		}

		/// <summary>
		/// Gets or sets the insert statement for the entity type.
		/// </summary>
		public string InsertStatement
		{
			get { return InsertCommand.Statement; }
			set { InsertCommand.Statement = value; }
		}

		/// <summary>
		/// Gets or sets the insert procedure for the entity type.
		/// </summary>
		public string InsertProcedure
		{
			get { return InsertCommand.Procedure; }
			set { InsertCommand.Procedure = value; }
		}

		/// <summary>
		/// Determines whether the entity type has an update command.
		/// </summary>
		/// <returns></returns>
		public bool HasUpdateCommand()
		{
			return HasCommandDef(ServerEntityCommandNames.Update);
		}

		private ServerEntityCommand _updateCommand;		
		/// <summary>
		/// Gets the update command for the entity type.
		/// </summary>
		public ServerEntityCommand UpdateCommand
		{
			get { return _updateCommand ?? (_updateCommand = GetCommandDef(ServerEntityCommandNames.Update)); }
		}
		
		/// <summary>
		/// Gets or sets the update statement for the entity type.
		/// </summary>
		public string UpdateStatement
		{
			get { return UpdateCommand.Statement; }
			set { UpdateCommand.Statement = value; }
		}

		/// <summary>
		/// Gets or sets the update procedure for the entity type.
		/// </summary>
		public string UpdateProcedure
		{
			get { return UpdateCommand.Procedure; }
			set { UpdateCommand.Procedure = value; }
		}
		
		/// <summary>
		/// Determines whether the entity type has a delete command.
		/// </summary>
		/// <returns></returns>
		public bool HasDeleteCommand()
		{
			return HasCommandDef(ServerEntityCommandNames.Delete);
		}

		private ServerEntityCommand _deleteCommand;
		/// <summary>
		/// Gets the delete command for the entity type.
		/// </summary>
		public ServerEntityCommand DeleteCommand
		{
			get { return _deleteCommand ?? (_deleteCommand = GetCommandDef(ServerEntityCommandNames.Delete)); }
		}
		
		/// <summary>
		/// Gets or sets the delete statement for the entity type.
		/// </summary>
		public string DeleteStatement
		{
			get { return DeleteCommand.Statement; }
			set { DeleteCommand.Statement = value; }
		}

		/// <summary>
		/// Gets or sets the delete procedure for the entity type.
		/// </summary>
		public string DeleteProcedure
		{
			get { return DeleteCommand.Procedure; }
			set { DeleteCommand.Procedure = value; }
		}

		private OptimisticConcurrencyMode _concurrencyMode = OptimisticConcurrencyMode.All;
		/// <summary>
		/// Gets or sets the concurrency mode to use for the entity type.
		/// </summary>
		/// <remarks>
		/// The default concurrency mode is <c>OptimisticConcurrencyMode.All</c>.</remarks>
		public OptimisticConcurrencyMode ConcurrencyMode
		{
			get { return _concurrencyMode; }
			set { _concurrencyMode = value; }
		}
		
		/// <summary>
		/// Finishes initialization for the entity type.
		/// </summary>
		/// <remarks>
		/// Override this method to provide additional initialization for
		/// the entity type. This method is called as the last step of type 
		/// creation before the entity type provider returns the finished entity
		/// type.
		/// </remarks>
		public virtual void FinishInitialization()
		{
		}
		
		private ServerEntityEventHandlerCollection _eventHandlers;
		/// <summary>
		/// Gets the collection of event handlers associated with the entity type.
		/// </summary>
		public ServerEntityEventHandlerCollection EventHandlers
		{
			get { return _eventHandlers ?? (_eventHandlers = new ServerEntityEventHandlerCollection()); }
		}
		
		private void InvokeHandlers(EntityEvent entityEvent, EntityProvider provider, Entity entity)
		{
			if (_eventHandlers != null)
				foreach (var handler in _eventHandlers.Where(AHandler => AHandler.EntityEvent == entityEvent))
					handler.Invoke(provider, entity);
		}
		
		/// <summary>
		/// Occurs before a select command is invoked for the entity type.
		/// </summary>
		public event ServerEntityQueryDelegate BeforeSelect;

		/// <summary>
		/// Invokes the before select event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entityQuery">The query being invoked.</param>
		public virtual void DoBeforeSelect(EntityProvider provider, EntityQuery entityQuery)
		{
			if (BeforeSelect != null)
				BeforeSelect(provider, entityQuery);
		}
		
		/// <summary>
		/// Occurs after selecting an entity of this type.
		/// </summary>
		public event ServerEntityDelegate AfterSelect;
		
		/// <summary>
		/// Invokes the after select event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoAfterSelect(EntityProvider provider, Entity entity)
		{
			if (AfterSelect != null)
				AfterSelect(provider, entity);
				
			InvokeHandlers(EntityEvent.AfterSelect, provider, entity);
		}
		
		/// <summary>
		/// Occurs before inserting an entity of this type.
		/// </summary>
		public event ServerEntityDelegate BeforeInsert;
		
		/// <summary>
		/// Invokes the before insert event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoBeforeInsert(EntityProvider provider, Entity entity)
		{
			if (BeforeInsert != null)
				BeforeInsert(provider, entity);
				
			InvokeHandlers(EntityEvent.BeforeInsert, provider, entity);
		}

		/// <summary>
		/// Occurs after an insert for this entity type.
		/// </summary>
		public event ServerEntityDelegate AfterInsert;
		
		/// <summary>
		/// Invokes the after insert event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoAfterInsert(EntityProvider provider, Entity entity)
		{
			if (AfterInsert != null)
				AfterInsert(provider, entity);
				
			InvokeHandlers(EntityEvent.AfterInsert, provider, entity);
		}
		
		/// <summary>
		/// Occurs before an update for this entity type.
		/// </summary>
		public event ServerEntityDelegate BeforeUpdate;

		/// <summary>
		/// Invokes the before update event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoBeforeUpdate(EntityProvider provider, Entity entity)
		{
			if (BeforeUpdate != null)
				BeforeUpdate(provider, entity);
				
			InvokeHandlers(EntityEvent.BeforeUpdate, provider, entity);
		}

		/// <summary>
		/// Occurs after an update for this entity type.
		/// </summary>
		public event ServerEntityDelegate AfterUpdate;
		
		/// <summary>
		/// Invokes the after update event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoAfterUpdate(EntityProvider provider, Entity entity)
		{
			if (AfterUpdate != null)
				AfterUpdate(provider, entity);
				
			InvokeHandlers(EntityEvent.AfterUpdate, provider, entity);
		}

		/// <summary>
		/// Occurs before a delete for this entity type.
		/// </summary>
		public event ServerEntityDelegate BeforeDelete;
		
		/// <summary>
		/// Invokes the before delete event handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoBeforeDelete(EntityProvider provider, Entity entity)
		{
			if (BeforeDelete != null)
				BeforeDelete(provider, entity);
				
			InvokeHandlers(EntityEvent.BeforeDelete, provider, entity);
		}

		/// <summary>
		/// Occurs after a delete for this entity type.
		/// </summary>
		public event ServerEntityDelegate AfterDelete;

		/// <summary>
		/// Invokes the after delete handlers for this entity type.
		/// </summary>
		/// <param name="provider">The entity provider in context.</param>
		/// <param name="entity">The entity triggering the handler.</param>
		public virtual void DoAfterDelete(EntityProvider provider, Entity entity)
		{
			if (AfterDelete != null)
				AfterDelete(provider, entity);
				
			InvokeHandlers(EntityEvent.AfterDelete, provider, entity);
		}
	}
}
